package com.dbms.storage;

import com.dbms.common.RID;
import java.nio.ByteBuffer;
import java.util.Arrays;

/**
 * TablePage 类：管理数据库表中单个数据页的元组存储，负责元组的插入、查询、删除和更新操作。
 * 核心作用：作为表数据在磁盘和内存中的载体，维护元组的物理存储结构，实现高效的空间管理和数据访问。
 *
 * 表页面布局（总大小为DiskManager.PAGE_SIZE=4096字节）：
 * -----------------------------------------------------------------------------------
 * | 头部信息(12字节) | 元组数据区(可变长) | 空闲空间(可变长) | 槽位数组(每个槽8字节) |
 * -----------------------------------------------------------------------------------
 * 头部信息：
 * - 槽位数量(4字节)：记录当前页面的槽位总数（包括已删除的元组）
 * - 空闲指针(4字节)：指向数据区中当前空闲空间的起始位置（下一个元组的插入点）
 * - 下一页ID(4字节)：指向链表中的下一个数据页（实现跨页存储）
 *
 * 槽位数组（位于页面末尾，每个槽8字节）：
 * - 每个槽对应一个元组，存储元组在数据区的偏移量(4字节)和长度(4字节)
 * - 长度为负数表示元组已被删除（惰性删除机制）
 */
public class TablePage {

    /** 当前TablePage关联的内存页面对象，提供数据存储和并发控制 */
    private final Page page;
    /** 包装页面数据的字节缓冲区，用于便捷地读写基本数据类型 */
    private final ByteBuffer buffer;

    // 头部信息偏移量（从页面起始位置开始计算）
    /** 槽位数量的偏移量（头部第0-3字节） */
    private static final int OFFSET_SLOT_COUNT = 0;
    /** 空闲指针的偏移量（头部第4-7字节） */
    private static final int OFFSET_FREE_POINTER = 4;
    /** 下一页ID的偏移量（头部第8-11字节） */
    private static final int OFFSET_NEXT_PAGE_ID = 8;
    /** 头部总大小（12字节：4+4+4） */
    private static final int HEADER_SIZE = 12;

    // 槽位相关常量
    /** 每个槽位的大小（8字节：偏移量4字节 + 长度4字节） */
    private static final int SLOT_SIZE = 8;
    /** 无效页ID常量（表示链表末尾，无下一页） */
    public static final int INVALID_PAGE_ID = -1;

    /**
     * 构造方法：将内存页面包装为TablePage，初始化缓冲区
     * @param page 关联的内存页面对象
     */
    public TablePage(Page page) {
        this.page = page;
        this.buffer = ByteBuffer.wrap(page.getData()); // 包装页面数据为ByteBuffer
    }

    /**
     * 初始化表页面为空白状态（新分配页面时调用）
     * 作用：设置初始头部信息，准备接收元组数据
     */
    public void init() {
        setSlotCount(0); // 初始槽位数量为0（无元组）
        setFreeSpacePointer(HEADER_SIZE); // 空闲指针起始位置为头部结束处（数据区从头部后开始）
        setNextPageId(INVALID_PAGE_ID); // 初始无下一页
    }

    // --- 下一页ID操作（维护表数据页的链表结构） ---

    /**
     * 获取当前页面在表链表中的下一页ID
     * @return 下一页ID（INVALID_PAGE_ID表示无下一页）
     */
    public int getNextPageId() {
        return buffer.getInt(OFFSET_NEXT_PAGE_ID);
    }

    /**
     * 设置当前页面在表链表中的下一页ID
     * @param nextPageId 下一页的页面ID
     */
    public void setNextPageId(int nextPageId) {
        buffer.putInt(OFFSET_NEXT_PAGE_ID, nextPageId);
        page.setDirty(true); // 修改页面，标记为脏页
    }

    // --- 槽位数量操作（管理页面中的元组槽位） ---

    /**
     * 获取当前页面的槽位总数（包括已删除的元组）
     * @return 槽位数量
     */
    public int getSlotCount() {
        return buffer.getInt(OFFSET_SLOT_COUNT);
    }

    /**
     * 获取当前页面的ID（通过关联的Page对象）
     * @return 页面ID
     */
    public int getPageId() {
        return this.page.getPageId();
    }

    /**
     * 设置当前页面的槽位总数（内部使用，仅在新增槽位时调用）
     * @param slotCount 新的槽位数量
     */
    private void setSlotCount(int slotCount) {
        buffer.putInt(OFFSET_SLOT_COUNT, slotCount);
    }

    // --- 空闲空间管理（跟踪和更新页面中的可用空间） ---

    /**
     * 获取当前空闲指针位置（下一个元组的插入起始偏移量）
     * @return 空闲指针偏移量
     */
    private int getFreeSpacePointer() {
        return buffer.getInt(OFFSET_FREE_POINTER);
    }

    /**
     * 设置空闲指针位置（更新下一个元组的插入起始偏移量）
     * @param pointer 新的空闲指针偏移量
     */
    private void setFreeSpacePointer(int pointer) {
        buffer.putInt(OFFSET_FREE_POINTER, pointer);
    }

    // --- 槽位信息操作（获取和设置元组的偏移量与长度） ---

    /**
     * 获取指定槽位对应的元组在数据区的偏移量
     * @param slotNum 槽位编号（从0开始）
     * @return 元组数据的起始偏移量
     */
    private int getTupleOffset(int slotNum) {
        // 槽位数组在页面末尾，第n个槽的偏移量 = 页面大小 - (slotNum+1)*槽位大小
        return buffer.getInt(DiskManager.PAGE_SIZE - (slotNum + 1) * SLOT_SIZE);
    }

    /**
     * 设置指定槽位对应的元组在数据区的偏移量
     * @param slotNum 槽位编号
     * @param offset 元组数据的起始偏移量
     */
    private void setTupleOffset(int slotNum, int offset) {
        buffer.putInt(DiskManager.PAGE_SIZE - (slotNum + 1) * SLOT_SIZE, offset);
    }

    /**
     * 获取指定槽位对应的元组长度
     * @param slotNum 槽位编号
     * @return 元组长度（正数表示有效，负数表示已删除）
     */
    private int getTupleLength(int slotNum) {
        // 长度存储在槽位的后4字节
        return buffer.getInt(DiskManager.PAGE_SIZE - (slotNum + 1) * SLOT_SIZE + 4);
    }

    /**
     * 设置指定槽位对应的元组长度
     * @param slotNum 槽位编号
     * @param length 元组长度（正数表示有效，负数表示已删除）
     */
    private void setTupleLength(int slotNum, int length) {
        buffer.putInt(DiskManager.PAGE_SIZE - (slotNum + 1) * SLOT_SIZE + 4, length);
    }

    /**
     * 计算当前页面的可用空闲空间（可用于插入新元组的字节数）
     * @return 可用空间大小（字节）
     */
    private int getFreeSpace() {
        // 总可用空间 = （页面大小 - 槽位占用空间） - 已使用的数据区空间
        // 槽位占用空间 = 槽位数量 * 槽位大小；已使用数据区空间 = 空闲指针位置 - 头部大小
        return (DiskManager.PAGE_SIZE - getSlotCount() * SLOT_SIZE) - getFreeSpacePointer();
    }

    // --- 元组操作核心方法 ---

    /**
     * 插入元组到当前页面
     * 逻辑：1. 检查空间是否充足 2. 复制元组数据到空闲区域 3. 更新槽位信息 4. 标记脏页
     * @param tupleData 元组的字节数组数据
     * @return 新插入元组的RID（记录标识符）；空间不足时返回null
     */
    public RID insertTuple(byte[] tupleData) {
        // 检查是否有足够空间：需容纳元组数据 + 新槽位（8字节）
        if (getFreeSpace() < tupleData.length + SLOT_SIZE) {
            return null; // 空间不足，插入失败
        }

        int slotCount = getSlotCount(); // 当前槽位数量（新槽位将使用该编号）
        int freePointer = getFreeSpacePointer(); // 当前空闲指针位置（元组插入起始点）

        // 1. 将元组数据复制到数据区的空闲位置
        System.arraycopy(tupleData, 0, page.getData(), freePointer, tupleData.length);

        // 2. 更新空闲指针：移动到元组数据之后（下一个空闲位置）
        setFreeSpacePointer(freePointer + tupleData.length);

        // 3. 为新元组创建槽位：记录偏移量和长度
        setTupleOffset(slotCount, freePointer);
        setTupleLength(slotCount, tupleData.length);

        // 4. 槽位数量+1（新增一个槽位）
        setSlotCount(slotCount + 1);

        // 5. 标记页面为脏页（数据已修改）
        page.setDirty(true);

        // 返回新元组的RID（页面ID + 槽位编号）
        return new RID(page.getPageId(), slotCount);
    }

    /**
     * 根据槽位编号获取元组数据
     * @param slotNum 槽位编号
     * @return 元组的字节数组数据；槽位无效或元组已删除时返回null
     */
    public byte[] getTuple(int slotNum) {
        // 检查槽位编号有效性（0 <= slotNum < 槽位总数）
        if (slotNum < 0 || slotNum >= getSlotCount()) {
            return null;
        }

        int length = getTupleLength(slotNum);
        // 长度为负数表示元组已删除
        if (length < 0) {
            return null;
        }

        // 从槽位获取元组偏移量，读取对应长度的字节数据
        int offset = getTupleOffset(slotNum);
        byte[] tupleData = new byte[length];
        System.arraycopy(page.getData(), offset, tupleData, 0, length);

        return tupleData;
    }

    /**
     * 删除指定槽位的元组（惰性删除：仅标记，不实际释放空间）
     * @param slotNum 槽位编号
     * @return 操作是否成功（槽位无效时返回false）
     */
    public boolean deleteTuple(int slotNum) {
        // 检查槽位编号有效性
        if (slotNum < 0 || slotNum >= getSlotCount()) {
            return false;
        }

        int length = getTupleLength(slotNum);
        // 已删除的元组无需重复删除
        if (length < 0) {
            return true;
        }

        // 惰性删除：将长度标记为负数（表示元组已删除）
        setTupleLength(slotNum, -length);
        page.setDirty(true); // 标记脏页

        return true;
    }

    /**
     * 【核心新增】原地更新指定槽位的元组（仅支持同长度更新）
     * @param slotNum 槽位编号
     * @param newData 新的元组数据
     * @return 更新是否成功（槽位无效、元组已删除或长度不匹配时返回false）
     */
    public boolean updateTuple(int slotNum, byte[] newData) {
        // 检查槽位编号有效性
        if (slotNum < 0 || slotNum >= getSlotCount()) {
            return false;
        }

        int length = getTupleLength(slotNum);
        // 元组已删除（长度为负），无法更新
        if (length < 0) {
            return false;
        }

        // 简化实现：仅支持新旧数据长度相同的更新（避免空间重新分配）
        // 真实系统中需处理变长更新，可能涉及数据迁移和空间整理
        if (newData.length != length) {
            return false;
        }

        // 原地覆盖旧数据：获取元组偏移量，复制新数据到该位置
        int offset = getTupleOffset(slotNum);
        System.arraycopy(newData, 0, page.getData(), offset, newData.length);
        page.setDirty(true); // 标记脏页

        return true;
    }
}